package com.ccteam.admin.viewmodels.music

import android.app.Application
import android.net.Uri
import androidx.lifecycle.*
import com.arthenica.ffmpegkit.Level
import com.ccteam.admin.core.TaglibParser
import com.ccteam.admin.repositories.BitrateAdminRepository
import com.ccteam.admin.repositories.MusicAdminRepository
import com.ccteam.admin.repositories.UploadAdminRepository
import com.ccteam.admin.utils.FfmpegUtils
import com.ccteam.admin.utils.LoadMessage
import com.ccteam.admin.vo.Resource
import com.ccteam.model.music.*
import com.ccteam.network.ApiError
import com.ccteam.network.exception.ApiException
import com.orhanobut.logger.Logger
import com.qiniu.android.http.ResponseInfo
import com.qiniu.android.storage.UpCancellationSignal
import com.qiniu.android.storage.UpCompletionHandler
import com.qiniu.android.storage.UploadManager
import com.qiniu.android.storage.UploadOptions
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.json.JSONObject
import java.io.File
import java.io.IOException
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine

/**
 * @author Xiaoc
 * @since 2021/4/24
 *
 * 上传音乐ViewModel
 *
 * 负责基本的上传音源工作，其中包含了分割音源以及上传文件等功能
 */
@HiltViewModel
class UploadAudioViewModel @Inject constructor(
    private val uploadManager: UploadManager,
    application: Application,
    private val ffmpegUtils: FfmpegUtils,
    private val taglibParser: TaglibParser,
    private val uploadRepository: UploadAdminRepository,
    private val musicAdminRepository: MusicAdminRepository,
    private val bitrateAdminRepository: BitrateAdminRepository,
    savedStateHandle: SavedStateHandle
): AndroidViewModel(application) {

    /**
     * 是否开启选择音源文件按钮
     */
    private val _enableSelectAudioButton = MediatorLiveData<Boolean>()
    val enableSelectAudioButton: LiveData<Boolean> get() = _enableSelectAudioButton

    /**
     * 是否开启上传文件按钮
     */
    private val _enableUploadAudioButton = MediatorLiveData<Boolean>()
    val enableUploadAudioButton: LiveData<Boolean> get() = _enableUploadAudioButton

    /**
     * 当前选择的音源Uri
     */
    private val _audioUri = MutableLiveData<Uri>()
    val audioUri: LiveData<Uri> get() = _audioUri

    /**
     * 当前处理的log日志信息
     */
    private val _logMessage = MutableStateFlow("")
    val logMessage get() = _logMessage.asStateFlow()

    /**
     * 当前是否正在分割音源
     */
    private val _isSegmenting = MutableLiveData<LoadMessage>(LoadMessage.NotLoading)
    val isSegmenting: LiveData<LoadMessage> get() = _isSegmenting

    /**
     * 当前是否正在上传文件
     */
    private val _uploadMessage = MutableLiveData<LoadMessage>(LoadMessage.NotLoading)
    val uploadMessage: LiveData<LoadMessage> get() = _uploadMessage

    /**
     * 最外层的m3u8的文件名，在分割完后会返回此内容
     * 用于上传到七牛云后存储这个master上传成功回调地址
     */
    private var masterM3u8FileName: String = ""

    /**
     * 要上传的比特率列表，Int值与数据库对应的一致
     */
    private var uploadBitrates: List<Int> = emptyList()

    /**
     * 要上传的最大比特率的值
     */
    private var uploadMaxBitrate: Int = BITRATE_NO_TAG

    /**
     * 要上传到七牛云的文件列表
     */
    private var uploadFiles: List<File> = emptyList()

    private val musicId: String? = savedStateHandle["musicId"]

    @Volatile
    private var shouldCancel: Boolean = false

    init {
        _enableSelectAudioButton.addSource(_isSegmenting){
            _enableSelectAudioButton.value = it !is LoadMessage.Loading && _uploadMessage.value !is LoadMessage.Loading
            _enableUploadAudioButton.value = it is LoadMessage.Success && _uploadMessage.value !is LoadMessage.Loading
        }

        _enableSelectAudioButton.addSource(_uploadMessage){
            _enableSelectAudioButton.value = it !is LoadMessage.Loading && _isSegmenting.value !is LoadMessage.Loading
            _enableUploadAudioButton.value = it !is LoadMessage.Loading && _isSegmenting.value is LoadMessage.Success
        }
    }

    /**
     * Ffmpeg执行完毕或出错后的回调
     * success 是否成功
     * masterFileName 分割为HLS后的主m3u8文件名
     * bitrates 当前音源分割的不同比特率列表
     * files 分割完毕后所有分割后的File文件对象
     */
    private val sessionCallback:
                (Boolean,String,List<String>
                 ,List<File>) -> Unit = { success,masterFileName,bitrates,files ->
        if(success){
            viewModelScope.launch {
                _logMessage.emit("\nMasterM3u8:${masterFileName}\nBitrateList:$bitrates\nfiles:${files.size}")

            if(masterFileName.isEmpty() || bitrates.isNullOrEmpty() || files.isNullOrEmpty()){
                _logMessage.emit("\n分割音频出现错误，部分数据为空！")
                _isSegmenting.postValue(LoadMessage.NotLoading)
            } else {
                _logMessage.emit("\n分割音频成功！")

                masterM3u8FileName = masterFileName

                // 将音源内容转化为Int型的音源列表，用于更新服务器数据
                uploadBitrates = bitrates.map {
                    when(it){
                        BITRATE_SQ ->{
                            BITRATE_SQ_TAG
                        }
                        BITRATE_HQ ->{
                            BITRATE_HQ_TAG
                        }
                        BITRATE_NQ ->{
                            BITRATE_NQ_TAG
                        }
                        else ->{
                            BITRATE_NO_TAG
                        }
                    }
                }
                // 记录上传的最大比特率，用于更新服务器数据
                uploadMaxBitrate = when {
                    bitrates.contains(BITRATE_SQ) -> {
                        BITRATE_SQ_TAG
                    }
                    bitrates.contains(BITRATE_HQ) -> {
                        BITRATE_HQ_TAG
                    }
                    bitrates.contains(BITRATE_NQ) -> {
                        BITRATE_NQ_TAG
                    }
                    else -> {
                        BITRATE_NO_TAG
                    }
                }

                uploadFiles = files
                _isSegmenting.postValue(LoadMessage.Success)

            }
            }
        } else {
            _isSegmenting.postValue(LoadMessage.NotLoading)
        }
    }

    private val logCallback: (Long,Level,String) -> Unit = { sessionId,level,message ->
        _logMessage.value = message
    }

    fun setAudioUri(audioUri: Uri){
        _audioUri.value = audioUri
    }

    /**
     * 分割音频
     * 进行分割音频操作，在异步线程执行
     * 如果出现错误会实时更新 [logMessage]
     */
    fun segmentAudio(){
        viewModelScope.launch {
            segmentAudioWithFfmpeg()
        }
    }

    private suspend fun segmentAudioWithFfmpeg(){
        val application = getApplication<Application>()
        _isSegmenting.value = LoadMessage.Loading

        _audioUri.value?.let {
            try {
//                val mediaInfoAsync = viewModelScope.async(Dispatchers.IO){
                    val mediaInfoAsync = application.contentResolver.openFileDescriptor(it,"r")?.use { fd ->
                        taglibParser.getMediaTag(fd.detachFd())
                    }
//                }

                val mediaInfo = mediaInfoAsync ?: run {
                    _isSegmenting.value = LoadMessage.NotLoading
                    return
                }

                _logMessage.emit("当前音源比特率：${mediaInfo.bitrate}K")
                Logger.d("比特率：${mediaInfo.bitrate}")

                ffmpegUtils.audioSegmentByUri(it,
                    FfmpegUtils.Config(mediaInfo.bitrate),sessionCallback,logCallback)
            } catch (e: Exception){
                _logMessage.emit("尝试读写文件失败！")
                _isSegmenting.value = LoadMessage.NotLoading
            }
        }
    }

    /**
     * 上传音源内容到服务端
     *
     * 此操作在分割完视频后可进行
     * 步骤如下：
     * 1.上传文件到七牛云
     * 2.获取最顶级m3u8的文件访问地址
     * 3.进行服务端内容的信息更新
     *
     * 其中有一个环节出错，均视为上传失败
     */
    fun uploadAudio(){
        _uploadMessage.value = LoadMessage.Loading
        viewModelScope.launch {
            val result = uploadRepository.getMediaToken()
            // 检测token是否获取成功
            if(result !is Resource.Success || result.data.isNullOrEmpty()){
                _uploadMessage.value = LoadMessage.NotLoading
                _logMessage.emit("获取Token失败")
                return@launch
            }

            var masterM3u8Url = ""
            val listJob = uploadFiles.mapIndexed { index, file ->
                async(Dispatchers.IO){
                    // 使用回调协程，这样可以成功挂起异步任务，在异步任务完成后再通知协程继续运行
                    suspendCoroutine<UploadCompletionData> { cn ->
                        // 上传文件
                        uploadManager.put(
                            file,file.name,result.data, UpCompletionHandler { key, info, response ->
                                if(info.isOK){
                                    val fileUrl = response.getString("data")

                                    viewModelScope.launch {
                                        _logMessage.emit("\n上传成功（${index}/${uploadFiles.size}）:${fileUrl}")
                                    }
                                    // 判断如果上传的文件为主m3u8文件时，将这个地址存储起来，待会上传服务端要使用
                                    if(key == masterM3u8FileName){
                                        masterM3u8Url = fileUrl
                                    }
                                    cn.resume(UploadCompletionData(key,info,response))
                                } else {
                                    cn.resumeWithException(ApiException(ApiError.serverErrorCode,"上传文件失败"))
                                    // 上传失败取消后续上传工作
                                    this@launch.cancel()
                                }
                            },UploadOptions(null,null,false, null, UpCancellationSignal {
                                return@UpCancellationSignal shouldCancel
                            }
                        ))
                    }
                }
            }

            listJob.forEach {
                it.await()
            }

            // 如果master m3u8文件的网络地址不为空，说明主文件成功上传，则通知后端进行更新音频信息
            if(masterM3u8Url.isEmpty() || musicId.isNullOrEmpty()){
                _uploadMessage.value = LoadMessage.NotLoading

                _logMessage.emit("\nm3u8文件或音乐Id为空")
                return@launch
            }

            _logMessage.emit("\n上传完成")
            editAudioInfo(masterM3u8Url)
        }
    }

    /**
     * 更新文件信息到服务端
     *
     * 此操作在上传完毕七牛云文件后进行
     * 主要进行如下步骤：
     * 1.更新对应音乐ID的播放地址url和最高音质信息
     * 2.创建音源的信息，通过拼接fileKey告诉服务端具体的ts文件内容
     */
    private suspend fun editAudioInfo(masterM3u8Url: String){
        val musicId = this.musicId!!

        val editMusicResult = musicAdminRepository.editMusicInfo(MusicDTO(id = musicId,
            url = masterM3u8Url,maxBitrate = uploadMaxBitrate))

        // 检测修改音乐信息是否成功
        if(editMusicResult !is Resource.Success){
            _uploadMessage.value = LoadMessage.NotLoading

            _logMessage.emit("修改音乐信息失败：${editMusicResult.message}")
            return
        }


        val forNum = (uploadFiles.size - uploadBitrates.size - 1) / uploadBitrates.size

        val bitrateDTOList = uploadBitrates.map {
            val fileKeyBuilder = StringBuilder()
            for(index in 0 until forNum){
                fileKeyBuilder.append("${String.format("%03d",index)}.ts;")
            }
            _logMessage.emit("fileKey：${fileKeyBuilder}")
            BitrateDTO(it,musicId,fileKeyBuilder.toString())
        }

        val bitrateEditResult = bitrateAdminRepository.createBitrateList(bitrateDTOList)

        // 检测修改音源信息是否成功
        if(bitrateEditResult !is Resource.Success){
            _uploadMessage.value = LoadMessage.NotLoading

            _logMessage.emit("修改音乐信息失败：${bitrateEditResult.message}")
            return
        }

        _logMessage.emit("音源成功创建！")

        _uploadMessage.value = LoadMessage.Success
    }

    override fun onCleared() {
        super.onCleared()

        // 如果退出时仍然还在处理，则取消其任务
        if(_isSegmenting.value == LoadMessage.Loading){
            ffmpegUtils.cancel()
        }
        shouldCancel = true
    }

    data class UploadCompletionData(
        val key: String,
        val info: ResponseInfo,
        val response: JSONObject
    )

}